package com.spring.boot.demo.utils;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author ZhuYouBin
 * @version 1.0.0
 * @Date: 2022/11/8 20:41
 * @Description Redis分布式锁工具类
 */
@Component
public class RedisLock {

    // 定义 RedisTemplate 对象
    private static RedisTemplate<String, Object> redisTemplate;
    // 通过构造方法的方式，注入 RedisTemplate 对象
    public RedisLock(RedisTemplate<String, Object> redisTemplate) {
        RedisLock.redisTemplate = redisTemplate;
    }

    // 创建 LUA 脚本【用于解锁的脚本语句】
    private static final StringBuilder unlock_lua = new StringBuilder();
    static {
        // TODO 这里的 KEYS[1] 表示分布式锁对应的 key 值
        // TODO 这里的 ARGV[1] 表示当前线程的唯一标识，要和分布式锁对应的 value 值比较是否相等
        // 查询当前 key 对应的 value 值，是否为当前线程拥有的
        unlock_lua.append("if redis.call(\"get\", KEYS[1]) == ARGV[1] ");
        unlock_lua.append("then "); // 当前锁是当前线程拥有的，可以删除分布式锁
        unlock_lua.append("   return redis.call(\"del\", KEYS[1]) ");
        unlock_lua.append("else "); // 不是当前线程拥有的锁，删除失败
        unlock_lua.append("   return 0 ");
        unlock_lua.append("end ");
    }

    /**
     * 获取分布式锁的操作
     * @param key 锁对应的 key 值
     * @param requestId 当前请求线程唯一标识, 作为 value 值【例如：UUID】
     * @param expire 锁过期时间，单位ms
     * @return 加锁成功，则返回 true
     */
    public static boolean acquireLock(final String key, final String requestId, final long expire) {
        try {
            RedisCallback<String> callback = new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    // 获取 redis 命令对象
                    JedisCommands commands = (JedisCommands) redisConnection.getNativeConnection();
                    SetParams params = new SetParams();
                    params.nx(); // 只有键不存在，才能够设置
                    params.px(expire); // 设置过期时间，单位ms
                    // 执行命令【】
                    return commands.set(key, requestId, params);
                }
            };
            // 执行加锁命令
            Object result = redisTemplate.execute(callback);
            if (!Objects.isNull(result)) {
                // 执行结果不等于null，则说明执行成功
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 释放当前线程持有的分布式锁
     * @param key 锁对应的 key 值
     * @param requestId 当前请求线程唯一标识, 作为 value 值
     * @return 释放成功，则返回true
     */
    public static boolean releaseLock(final String key, final String requestId) {
        try {
            final List<String> keys = new ArrayList<>();
            keys.add(key);
            final List<String> args = new ArrayList<>();
            args.add(requestId);

            RedisCallback<Long> callback = new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    Object nativeConnection = redisConnection.getNativeConnection();
                    Jedis jedis = (Jedis) nativeConnection;
                    // 执行解锁脚本语句
                    Object eval = jedis.eval(unlock_lua.toString(), keys, args);
                    return (Long) eval;
                }
            };
            Long ret = (Long) redisTemplate.execute(callback);
            if (ret != null && ret > 0L) {
                // 删除成功，解锁成功
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

}
